// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeClientDataHandleThread.h"
#include <fstream>
#include <iostream>
#include <dlfcn.h>
#include "cJSON.h"
#include "../cae_common/CaeLogConfig.h"
#include "../cae_agent/CaeAgent.h"
#include "../cae_common/CaeAppCtrlCmdUtils.h"
#include "../cae_service/CaeEngineControl.h"
#include "../cae_dataPipe/CaeInput.h"
#include "../cae_CPHMediaEngine/CPHMediaEngine.h"
#include "../cae_common/CaeMsgCode.h"
#include "../cae_service/CaeSocketAgent.h"

constexpr int CLIENT_DATA_HANDLER_THREAD_SLEEP_INTERVAL = 500;

CaeClientDataHandleThread::CaeClientDataHandleThread(const std::weak_ptr<CaeSocketAgent>& socketAgent)
{
    m_socketAgent = socketAgent;
};

CaeClientDataHandleThread::~CaeClientDataHandleThread() = default;

void CaeClientDataHandleThread::HandleCompleteClientPktMsg(StreamMsgHead *msg, uint8_t *buff,
                                                           uint32_t pktStartPos)
{
    if (!m_socketAgent.expired()) {
        std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
        socketAgent->UpdateErrorTimerTime();
    }

    if (msg == nullptr) {
        LOGE("Msg is null.");
        return;
    }
    switch (msg->type) {
        case CAE_MSG_APK_HEART_BEAT: {
            map<string, string> params = { { KEY_COMMAND, to_string(CMD_HEARTBEAT_RESPONSE) } };
            string cmdStr = CaeAppCtrlCmdUtils::GenerateCommand(params);
            size_t strLen = cmdStr.size() + 1;
            uint8_t *responseBuff;
            responseBuff = (uint8_t *)malloc(strLen + sizeof(STREAM_MSG_HEAD));
            if (nullptr == responseBuff) {
                LOGE("Failed to malloc control response.");
                return;
            }
            StreamMsgHead msgHead {};
            uint8_t msgType = CAE_MSG_APK_HEART_BEAT;
            msgHead.type = msgType;
            msgHead.SetPayloadSize((uint32_t) strLen);
            msgHead.magicWord = CAE_STREAM_DELIMITER_MAGICWORD;
            msgHead.checksum = (uint8_t)GET_CAE_CHECKSUM(msgType);
            if (EOK != memcpy_s(responseBuff, sizeof(STREAM_MSG_HEAD), &msgHead, sizeof(STREAM_MSG_HEAD))) {
                LOGE("Failed to memcpy response from msg head.");
            }
            if (EOK != memcpy_s(responseBuff + sizeof(STREAM_MSG_HEAD), strLen, cmdStr.c_str(), strLen)) {
                LOGE("Failed to memcpy response from cmdStr.");
            }

            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                int bytes = socketAgent->Send(responseBuff, strLen + sizeof(STREAM_MSG_HEAD));
                if (bytes <= 0) {
                    LOGE("CaeSocketAgent failed to send response.");
                }
            }

            free(responseBuff);
            responseBuff = nullptr;
            break;
        }
        case CAE_MSG_TOUCH: {
            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                socketAgent->UpdateNoTouchTimer();
            }
            CaeTouchEventMsg *touchMsg = (CaeTouchEventMsg *)(buff + pktStartPos + sizeof(STREAM_MSG_HEAD));
            CaeEngineControl::GetInstance().HandleTouchMsg(touchMsg);

            break;
        }
        case CAE_MSG_KEY_EVENT: {
            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                socketAgent->UpdateNoTouchTimer();
            }
            CaeKeyEventMsg *keyMsg = (CaeKeyEventMsg *) (buff + pktStartPos + sizeof(STREAM_MSG_HEAD));
            CaeEngineControl::GetInstance().HandleKeyMsg(keyMsg);

            if (keyMsg->GetAction() == 1 && keyMsg->GetKeycode() == 4 && IsBackToHome()) {
                LOGI("Back to home when window is desk or lockscreen");
                if (!CaeClientNotifyTool::GetInstance().SendMsgToClient(CAE_BACK_HOME, m_socketAgent)) {
                    LOGE("Failed to send back home message.");
                }
            }
            break;
        }
        case CAE_MSG_MOTION_EVENT: {
            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                socketAgent->UpdateNoTouchTimer();
            }
            LOGD("Received MotionEvent.");
            CaeEngineControl::GetInstance().m_cphMediaEngine->InjectAxisData(buff + pktStartPos + sizeof(STREAM_MSG_HEAD),
                                                                             (const uint64_t)msg->GetPayloadSize());
            break;
        }
        case CAE_MSG_CONTROL: {
            if (msg->GetPayloadSize() <= 0) {
                LOGE("Failed to append control msg , because of payload size is 0.");
                break;
            }
            size_t controlDataSize = msg->GetPayloadSize();
            uint8_t *controlData = buff + pktStartPos + sizeof(STREAM_MSG_HEAD);

            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                socketAgent->AddCmdPkt(controlData, controlDataSize);
            }
            break;
        }
        case CAE_MSG_CHANNEL: {
            LOGE("Channel data is not supported.");
            break;
        }
        case CAE_MSG_IME: {
            if (msg->GetPayloadSize() <= 0) {
                LOGE("Failed to append ime msg , because of payload size is less than 0.");
                break;
            }
            size_t imeDataSize = msg->GetPayloadSize();
            uint8_t *imeData = buff + pktStartPos + sizeof(STREAM_MSG_HEAD);
            CaePipeManager::GetInstance().AppendData(imeData, imeDataSize, false, CAE_MSG_IME);
            break;
        }
        case CAE_MSG_VIRTUAL_CAMERA:
        case CAE_MSG_VIRTUAL_MICROPHONE:
        case CAE_MSG_VIRTUAL_SENSOR:
        case CAE_MSG_VIRTUAL_LOCATION: {
            VmiDeviceType deviceType = (VmiDeviceType)(msg->type - CAE_MSG_VIRTUAL_CAMERA);
            if (msg->GetPayloadSize() <= 0) {
                LOGE("Failed to send control msg , because of payload size is 0.");
                break;
            }
            uint32_t virtualDeviceDataSize = msg->GetPayloadSize();
            uint8_t *virtualDeviceData = buff + pktStartPos + sizeof(STREAM_MSG_HEAD);

            // LOCATION数据包含gnss location定位和measurement卫星两种不同类型数据，其他虚拟设备只包含一种，该处LOCATION数据需要单独处理
            if (deviceType != LOCATION) {
                CaeEngineControl::GetInstance().VmiDeviceSend(deviceType, virtualDeviceData, virtualDeviceDataSize);
            } else {
                // 处理LOCATION数据
                CaeEngineControl::GetInstance().HandleLocationMsg(virtualDeviceData, virtualDeviceDataSize);
            }
            break;
        }
        default:
            LOGE("Unknown msg type %d.", msg->type);
            if (!m_socketAgent.expired()) {
                std::shared_ptr<CaeSocketAgent> socketAgent = m_socketAgent.lock();
                CaeAgent::GetInstance().AddIpToBlacklist(socketAgent->GetClientIp());
                socketAgent->SetSocketAgentState(CAE::WAIT_CLOSE);
            }

            CaeAgent::GetInstance().CloseClient();
            break;
    }
}

bool CaeClientDataHandleThread::IsBackToHome()
{
    char dumpsysRes[1024];
    memset_s(dumpsysRes, sizeof(dumpsysRes), '\0', sizeof(dumpsysRes));

    string dumpsysCmd = "dumpsys window | grep -E 'mCurrentFocus|mIsShowing'";
    string lockScreen = "mIsShowing=true";
    string deskLauncher = CaeConfigManage::GetDeskLauncher();
    bool isBackToHome = false;

    FILE *fp = popen(dumpsysCmd.c_str(), "r");
    while (fgets(dumpsysRes, sizeof(dumpsysRes) - 1, fp) != nullptr) {
        string dumpsysResultStr = string(dumpsysRes);
        if (dumpsysResultStr.find(deskLauncher) != string::npos || dumpsysResultStr.find(lockScreen) != string::npos) {
            isBackToHome = true;
            break;
        }
    }
    pclose(fp);
    return isBackToHome;
}

void CaeClientDataHandleThread::CaeClientDataAcceptTaskEntry(CaeClientDataHandleThread *clientDataHandleThread)
{
    uint8_t *buff;
    buff = (uint8_t *)malloc(DATA_SHAREMEM_SIZE);
    if (nullptr == buff) {
        return;
    }
    uint32_t receivedDataLen = 0;
    uint32_t dataCompleteLen = 0;
    bool dataCompleteFlag = true;
    StreamMsgHead *msg = nullptr;
    while (clientDataHandleThread->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        if (receivedDataLen >= DATA_SHAREMEM_SIZE) {
            LOGE("No space left to receive data from client sdk.");
            break;
        }
        int ret = -1;
        if (!clientDataHandleThread->m_socketAgent.expired()) {
            std::shared_ptr<CaeSocketAgent> socketAgent = clientDataHandleThread->m_socketAgent.lock();
            ret = socketAgent->Recv(buff + receivedDataLen, DATA_SHAREMEM_SIZE - receivedDataLen);
        } else {
            LOGE("socketAgent is invalid..");
        }

        if (ret <= 0) {
            usleep(1000);
            continue;
        }
        if (dataCompleteFlag) {
            msg = (StreamMsgHead *)buff;
            dataCompleteLen = msg->GetPayloadSize() + sizeof(STREAM_MSG_HEAD);
        }
        receivedDataLen += static_cast<uint32_t>(ret);
        if (receivedDataLen < dataCompleteLen) {
            LOGD("Already have received data: %d, not complete.", receivedDataLen);
            dataCompleteFlag = false;
            continue;
        } else if (receivedDataLen > dataCompleteLen) {
            LOGD("Data length is too long, receiveDataLen: %d, completeLen should is %d.", receivedDataLen,
                dataCompleteLen);
            uint32_t remainLen = receivedDataLen;
            uint32_t pktStartPos = 0;
            int index = 0;
            while (remainLen >= sizeof(STREAM_MSG_HEAD)) {
                StreamMsgHead *tmpMsgHead;
                tmpMsgHead = (StreamMsgHead *)(buff + pktStartPos);
                uint32_t pktLen = tmpMsgHead->GetPayloadSize() + sizeof(STREAM_MSG_HEAD);
                if (pktLen <= remainLen) {
                    LOGD("Handle multi pkt, index: %d.", index);
                    HandleCompleteClientPktMsg(tmpMsgHead, buff, pktStartPos);
                    remainLen -= pktLen;
                    pktStartPos += pktLen;
                    index++;
                    continue;
                } else {
                    LOGD("Handle multi pkt complete.");
                    break;
                }
            }

            if (remainLen > 0) {
                if (pktStartPos > remainLen) {
                    if (EOK != memcpy_s(buff, DATA_SHAREMEM_SIZE, buff + pktStartPos, remainLen)) {
                        LOGE("Handle not complete pkt data, failed to memcpy.");
                    }
                } else {
                    if (EOK != memmove_s(buff, DATA_SHAREMEM_SIZE, buff + pktStartPos, remainLen)) {
                        LOGE("Handle not complete pkt data, failed to memmove.");
                    }
                }
            }
            receivedDataLen = remainLen;
            dataCompleteLen = 0;
            dataCompleteFlag = true;
        } else {
            dataCompleteLen = 0;
            receivedDataLen = 0;
            dataCompleteFlag = true;
            HandleCompleteClientPktMsg(msg, buff, 0);
        }
        usleep(CLIENT_DATA_HANDLER_THREAD_SLEEP_INTERVAL);
    }
    free(buff);
    buff = nullptr;
    clientDataHandleThread->m_threadStatus = CAE_THREAD_STATUS_EXIT;
}

/**
 * 开启接收压缩数据的线程
 * @return
 */
void CaeClientDataHandleThread::Run()
{
    char threadName[] = "ClientDataHandle";
    prctl(PR_SET_NAME, threadName);
    LOGI("CaeClientDataHandle thread RUN.");
    CaeClientDataAcceptTaskEntry(this);
    LOGI("CaeClientDataHandle thread exit.");
}